package com.example.multitenant.multitenant.config

import com.example.multitenant.base.Log
import com.example.multitenant.multitenant.model.Tenant
import com.example.multitenant.multitenant.model.Tenants
import liquibase.Contexts
import liquibase.database.DatabaseFactory
import liquibase.database.jvm.JdbcConnection
import liquibase.resource.ClassLoaderResourceAccessor
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
import org.springframework.context.annotation.Lazy
import org.springframework.stereotype.Component
import javax.annotation.PostConstruct
import javax.sql.DataSource

/**
 * @author xiang
 * @Date 2022/8/29 11:05
 **/

@Component
class DataSources {

    companion object {
        private const val TAG = "DataSources"
    }

    @Autowired(required = false)
    private lateinit var dataSourceProperties: DataSourceProperties

    @Autowired
    private lateinit var tenantProperties: MultiTenantProperties

    @Lazy
    @Autowired
    private lateinit var tenants: Tenants

    private val dataSources = HashMap<String, DataSource>()

    fun insertDefaultTenant() {
        val exist = tenants.findAll().find {
            it.dbName == tenantProperties.defaultDb
        } != null
        if (!exist) {
            tenants.save(
                Tenant(
                    groupId = tenantProperties.defaultGroup,
                    dbName = tenantProperties.defaultDb
                )
            )
        }
    }

    fun initTenantDataSources() {
        tenants.findAll().forEach {
            if (!dataSources.containsKey(it.dbName)) {
                val jdbcUrl = replaceJdbcDb(dataSourceProperties.url, it.dbName)
                val datasource = createDatasource(jdbcUrl)
                dataSources[it.dbName] = datasource
                runLiquibase(datasource, tenantProperties.tenantChangelog)
            }
        }
    }

    fun tenantDataSource(tenantId: String?): DataSource {
        return tenantId?.let {
            dataSources[it]
        } ?: dataSources[tenantProperties.defaultDb]!!
    }

    fun masterDataSource() = dataSources[tenantProperties.masterDb]!!

    @PostConstruct
    private fun initMasterDataSource() {
        val dbName = tenantProperties.masterDb
        val jdbcUrl = replaceJdbcDb(dataSourceProperties.url, dbName)
        val masterDataSource = createDatasource(jdbcUrl)
        dataSources[tenantProperties.masterDb] = masterDataSource
        runLiquibase(masterDataSource, tenantProperties.masterChangelog)
    }

    /**
     * 替换 jdbc url 中的数据库名称
     */
    private fun replaceJdbcDb(url: String, dbName: String): String {
        val start = url.lastIndexOf('/') + 1
        val end = url.indexOf('?')
        val db = url.substring(start, end)
        return url.replace(db, dbName)
    }

    private fun createDatasource(jdbcUrl: String): DataSource {
        Log.i(TAG, "createDatasource: $jdbcUrl")
        return dataSourceProperties.initializeDataSourceBuilder()
            .url(jdbcUrl)
            .build()
    }

    private fun runLiquibase(dataSource: DataSource, changelog: String) {
        Log.i(TAG, "runLiquibase")
        val connection = JdbcConnection(dataSource.connection)
        val database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(connection)
        val liquibase = liquibase.Liquibase(
            changelog,
            ClassLoaderResourceAccessor(),
            database
        )
        liquibase.update(Contexts())
    }
}